package cn.tangcent;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by TangMing on 2017/4/8.
 */
public class SudokuExecutor {
    private SudokuData sudokuData;

    private List<SudokuRule> sudokuRules;

    public SudokuExecutor(SudokuData sudokuData, List<SudokuRule> sudokuRules) {
        this.sudokuData = sudokuData;
        this.sudokuRules = sudokuRules;
    }

    private volatile int flag = 0;

    private volatile int failed = 0;

    private Lock lock = new ReentrantLock();

    private Condition completedCondition = lock.newCondition();

    private Condition changeCondition = lock.newCondition();

    private volatile int lasted = -1;

    private volatile boolean completed = false;

    private AtomicInteger counter = new AtomicInteger(0);

    private ExecutorService executorService;

    public void start() {
        failed = (1 << sudokuRules.size()) - 1;
        executorService = Executors.newFixedThreadPool(sudokuRules.size());
        for (int i = 0; i < sudokuRules.size(); i++) {
            executorService.submit(new SudokuRuleTask(sudokuRules.get(i), i));
        }
        new Thread(() -> {
            while (true) {
                if (flag == failed || sudokuData.completed()) {
                    lock.lock();
                    try {
                        //DCL
                        completed = true;
                        completedCondition.signalAll();
                        executorService.shutdownNow();
                        return;
                    } finally {
                        lock.unlock();
                    }
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ignored) {
                    return;
                }
            }
        }).start();
    }


    public void waitCompleted() {
        lock.lock();
        try {
            if (!completed) {
                completedCondition.await();
            }
        } catch (InterruptedException ignored) {
        } finally {
            lock.unlock();
        }
    }

    public int[][] result() {
        waitCompleted();
        return sudokuData.result();
    }

    private class SudokuRuleTask implements Runnable {

        private SudokuRule sudokuRule;
        private int index;

        private int before;

        public SudokuRuleTask(SudokuRule sudokuRule, int index) {
            this.sudokuRule = sudokuRule;
            this.index = 1 << index;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    before = counter.getAndIncrement();
                    if (sudokuRule.check(sudokuData)) {
                        lock.lock();
                        try {
                            lasted = counter.getAndIncrement();
                            flag = 0;
                            changeCondition.signalAll();
                        } finally {
                            lock.unlock();
                        }
                    } else {
                        if (before > lasted) {
                            lock.lock();
                            try {
                                //DCL
                                if (before > lasted) {
                                    flag |= index;
                                    changeCondition.await();
                                }
                            } catch (InterruptedException e) {
                                return;
                            } finally {
                                lock.unlock();
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
